/* -*- c++ -*- */

#ifndef _BOX_H_
#define _BOX_H_

#include <math.h>
#include "Punto.h"
#include "oglArrays.h"

// windows defines the min and max macros !!!!!
#ifdef _WIN32
#ifdef min 
#undef min
#endif

#ifdef max 
#undef max
#endif
#endif // _WIN32

class Box {
 public:
  Box( void): _min( MAXFLOAT, MAXFLOAT, MAXFLOAT), _max( -MAXFLOAT, -MAXFLOAT, -MAXFLOAT) {};
  Box( const Punto &min, const Punto &max): _min( min), _max( max) {};
  Box( const Box &box): _min( box._min), _max( box._max) {};

  Box &operator=( const Box &box) {
    _min = box._min;
    _max = box._max;
    return *this;
  }

  void init( const Punto &p) { _min = p; _max = p; }
  void init( float x, float y, float z = 0.0f) { 
    _min.set( x, y, z); 
    _max.set( x, y, z);
  }
  void init( double x, double y, double z = 0.0) { 
    _min.set( x, y, z); 
    _max.set( x, y, z);
  }
  void init( const Box &box) {
    _min = box._min;
    _max = box._max;
  }

  void update( const Punto &p) {
    if ( p.x() < _min.x()) _min.x() = p.x();
    if ( p.y() < _min.y()) _min.y() = p.y();
    if ( p.z() < _min.z()) _min.z() = p.z();
    if ( p.x() > _max.x()) _max.x() = p.x();
    if ( p.y() > _max.y()) _max.y() = p.y();
    if ( p.z() > _max.z()) _max.z() = p.z();
  }
  void update( float x, float y, float z = 0.0f) {
    if ( x < _min.x()) _min.x() = x;
    if ( y < _min.y()) _min.y() = y;
    if ( z < _min.z()) _min.z() = z;
    if ( x > _max.x()) _max.x() = x;
    if ( y > _max.y()) _max.y() = y;
    if ( z > _max.z()) _max.z() = z;
  }
  void update( double x, double y, double z = 0.0) { update( x, y, z);}
  void update( const Box &box) {
    update( box.min());
    update( box.max());
  }

  float diagonal2() const { Vector dif = _max - _min; return dif.mod2(); }
  float diagonal() const { Vector dif = _max - _min; return dif.mod(); }

  Punto min() const { return _min;}
  Punto max() const { return _max;}
  Punto centro() const { Punto c = 0.5 * ( _min + _max); return c;}
  Punto diff() const { return _max - _min;}
  
  void draw() const;

  bool dentro( const Punto &p) const { return dentro( p.x(), p.y(), p.z());}
  bool dentro( float x, float y, float z = 0.0f) const;
  bool dentro( float x, float y, float z, float eps) const;
  bool dentro( const Punto &p, float eps) const { return dentro( p.x(), p.y(), p.z(), eps);}
  bool dentroEPS( const Punto &p) const { return dentroEPS( p.x(), p.y(), p.z());}
  bool dentroEPS( float x, float y, float z = 0.0f) const;

  Box get_bounding_cube() const {
    Punto dif = _max - _min;
    float max = dif.x();
    if ( dif.y() > max) max = dif.y();
    if ( dif.z() > max) max = dif.z();
    dif.set( max, max, max);
    return Box( _min, _min + dif);
  }

  Box get_centered_bounding_cube() const {
    Punto dif = _max - _min;
    float max = dif.x();
    if ( dif.y() > max) max = dif.y();
    if ( dif.z() > max) max = dif.z();
    // we want a centered cube
    dif.set( max - dif.x(), max - dif.y(), max - dif.z());
    dif *= 0.5f;
    return Box( _min - dif, _max + dif);
  }

 private:
  Punto _min, _max;
};

inline void Box::draw() const {
  CheckGLError( "Box::draw begin");
  glBegin( GL_LINES);
 
  glVertex3f( _min.x(), _min.y(), _min.z());
  glVertex3f( _max.x(), _min.y(), _min.z());
 
  glVertex3f( _max.x(), _min.y(), _min.z());
  glVertex3f( _max.x(), _max.y(), _min.z());
 
  glVertex3f( _max.x(), _max.y(), _min.z());
  glVertex3f( _min.x(), _max.y(), _min.z());
 
  glVertex3f( _min.x(), _max.y(), _min.z());
  glVertex3f( _min.x(), _min.y(), _min.z());
 
  glVertex3f( _min.x(), _min.y(), _max.z());
  glVertex3f( _max.x(), _min.y(), _max.z());
 
  glVertex3f( _max.x(), _min.y(), _max.z());
  glVertex3f( _max.x(), _max.y(), _max.z());
 
  glVertex3f( _max.x(), _max.y(), _max.z());
  glVertex3f( _min.x(), _max.y(), _max.z());
 
  glVertex3f( _min.x(), _max.y(), _max.z());
  glVertex3f( _min.x(), _min.y(), _max.z());
 
  glVertex3f( _min.x(), _max.y(), _min.z());
  glVertex3f( _min.x(), _max.y(), _max.z());
 
  glVertex3f( _max.x(), _max.y(), _min.z());
  glVertex3f( _max.x(), _max.y(), _max.z());
 
  glVertex3f( _min.x(), _min.y(), _min.z());
  glVertex3f( _min.x(), _min.y(), _max.z());
 
  glVertex3f( _max.x(), _min.y(), _min.z());
  glVertex3f( _max.x(), _min.y(), _max.z());
 
  glEnd();
  CheckGLError( "Box::draw end");
}

inline bool Box::dentro( float x, float y, float z) const {
  bool ret = false;
  if ( ( x >= _min.x()) && ( x <= _max.x()) &&
       ( y >= _min.y()) && ( y <= _max.y()) &&
       ( z >= _min.z()) && ( z <= _max.z()) )
    ret = true;
  return ret;
}

inline bool Box::dentroEPS( float x, float y, float z) const {
  float eps = ( float)( EPSILON_CMP * diagonal());
  bool ret = false;
  if ( ( x > _min.x() - eps) && ( x < _max.x() + eps) &&
       ( y > _min.y() - eps) && ( y < _max.y() + eps) &&
       ( z > _min.z() - eps) && ( z < _max.z() + eps) )
    ret = true;
  return ret;
}

inline bool Box::dentro( float x, float y, float z, float eps) const {
  return ( ( x >= _min.x() - eps) && ( x <= _max.x() + eps) &&
	   ( y >= _min.y() - eps) && ( y <= _max.y() + eps) &&
	   ( z >= _min.z() - eps) && ( z <= _max.z() + eps));
}

#endif // _BOX_H_
